/**************************************************************************//**
 * @file     drv_vcom.c
 * @version  V1.00
 * $Revision: 1 $
 * $Date: 21/12/28 14:04p $
 * @brief    NUC970 VCOM driver source file
 *
 * @note
 * SPDX-License-Identifier: Apache-2.0
 * @Copyright (C) 2021 THEWON. All rights reserved.
*****************************************************************************/
#include <rtconfig.h>

#include <rtdevice.h>
#include "drv_uartX.h"
#include "drv_sys.h"
#include "drv_sys.h"
#include "nuc970_sys.h"
#include "nuc970_usbd.h"
#include "vcom_serial.h"

//#define DRV_DEBUG
#define LOG_TAG             "drv.vcom"
#include <drv_log.h>

struct _vcom_device vcom_device;

static rt_err_t rt_vcom_init(struct rt_device *dev)
{
    vcom_device.bulkInReady_sem = rt_sem_create("bulkin", 1, RT_IPC_FLAG_PRIO);
    vcom_device.bulkOutReady_sem = rt_sem_create("bulkout", 0, RT_IPC_FLAG_PRIO);

    return RT_EOK;
}

static rt_err_t rt_vcom_open(struct rt_device *dev, rt_uint16_t oflag)
{
    vcom_device.gUsbRxBuf = rt_malloc(VCOM_RX_BUF_SIZE);

    USBD_Open(&gVCOMInfo, VCOM_ClassRequest, NULL);

    /* Endpoint configuration */
    VCOM_Init();
    /* Enable interrupt. */
    rt_hw_interrupt_install(USBD_IRQn, USBD_IRQHandler, (void *)(0), "vcom");
    rt_hw_interrupt_set_priority(USBD_IRQn, IRQ_LEVEL_3);
    rt_hw_interrupt_set_type(USBD_IRQn, HIGH_LEVEL_SENSITIVE);
    rt_hw_interrupt_umask(USBD_IRQn);

    return RT_EOK;
}

static rt_err_t rt_vcom_close(struct rt_device *dev)
{
    rt_hw_interrupt_mask(USBD_IRQn);
    rt_free(vcom_device.gUsbRxBuf);

    return RT_EOK;
}

static rt_size_t rt_vcom_read(struct rt_device *dev,
                                rt_off_t          pos,
                                void             *buffer,
                                rt_size_t         size)
{
    rt_sem_take(vcom_device.bulkOutReady_sem, RT_WAITING_FOREVER);
    if (size > vcom_device.vcom_rcv_off) {
        size = vcom_device.vcom_rcv_off;
    }
    rt_memcpy(buffer, vcom_device.gUsbRxBuf, size);
    vcom_device.vcom_rcv_off = 0;
    
    return size;
}

static rt_size_t rt_vcom_write(struct rt_device *dev,
                                 rt_off_t          pos,
                                 const void       *buffer,
                                 rt_size_t         size)
{
    rt_uint8_t *out_buf;
    int i;
    
    out_buf = (rt_uint8_t *)buffer;
    rt_sem_take(vcom_device.bulkInReady_sem, RT_WAITING_FOREVER);

    for (i = 0; i < size; i++)
        USBD->EP[EPA].ep.EPDAT_BYTE = out_buf[i];
    USBD->EP[EPA].EPRSPCTL = USB_EP_RSPCTL_SHORTTXEN;    // packet end
    USBD->EP[EPA].EPTXCNT = size;
    USBD_ENABLE_EP_INT(EPA, USBD_EPINTEN_INTKIEN_Msk);

    return size;
}
int rt_hw_vcom_register(void)
{
    rt_err_t ret;
    struct rt_device *device;

    device = &vcom_device.parent;

    device->type        = RT_Device_Class_Char;
    device->rx_indicate = RT_NULL;
    device->tx_complete = RT_NULL;

    device->init        = rt_vcom_init;
    device->open        = rt_vcom_open;
    device->close       = rt_vcom_close;
    device->read        = rt_vcom_read;
    device->write       = rt_vcom_write;
    device->flush       = RT_NULL;
    device->control     = RT_NULL;

    /* register a character device */
    ret = rt_device_register(device, "vcom", RT_DEVICE_FLAG_RDWR);

    return ret;
}
INIT_DEVICE_EXPORT(rt_hw_vcom_register);
